/**
 * Defines the standard way to represent the configuration data in edgy's
 * YAML format.
 *
 * Shared by the parser and the writer.
 */

#ifndef _CONFIGDATA_H
#define	_CONFIGDATA_H

using namespace std;

#include <string>
#include <map>
#include <vector>
#include <utility> // pair<>
#include <exception>
#include <stdexcept>

#include "edgy.h"
#include "yaml/yaml.h"



/*
 * The parser processes events from libyaml and creates an internal
 * representation of the data. In order to create a configdata struct, it is
 * necessary to call createObj() on the RootMapping instance. It will then
 * recursively call createObj() on its children and so on. Each child has its
 * own createObj() signature, but they all simply populate the structure
 * they're passed or they return a new instance of some object (usually one of
 * edgy's classes).
 */




/** Defines the menu-map structure in the config file. */
typedef map<string, vector<menu_item*> > menu_map;
/** Defines the cursor-map structure in the config file. */
typedef map<string, vector<string> > cursor_map;

// the entire config can be placed in this struct
/**
 * Models the config file in a single struct. This is all kept in memory right
 * now. The parser's getConfig() returns an instance of this.
 */
struct configdata {
  menu_map menus;
  cursor_map cursors;
  vector<effect*> effects;
};

/** Any config errors encountered during parsing result in this exception. */
class BadConfigException : public exception {
public:
  BadConfigException(char*) throw();
  BadConfigException(string) throw();
  virtual ~BadConfigException() throw();
  virtual const char* what() const throw();
private:
  string reason;
};

class VirtualItem;

/**
 * This is only used by the VirtualItem below. This class's member functions
 * get called when there isn't an appropriate member function found in a
 * derived class at run-time, which signals either a configuration error or an
 * internal parsing error.
 * This class must implement all functions that we'd like to be able to call
 * in the derived classes without knowing the type at run-time.
 */
class ConfigItem {
public:
  ConfigItem();
  virtual ~ConfigItem();
  
  /**
   * This is called when the parser issues a mapping-open event. This should
   * return an instance of a class that would handle the mapping until
   * it's closed.
   */
  virtual VirtualItem* getMapping() throw(BadConfigException);
  
  /**
   * This is called when the parser issues a sequence-open event. This should
   * return an instance of a class that would handle the sequence until it's
   * closed.
   */
  virtual VirtualItem* getSequence() throw(BadConfigException);
  
  /**
   * This is called when the item we returned in getMapping/getSequence
   * is done processing the mapping/sequence. It's popped off the stack and
   * return to us so that we could store it somewhere and check its correctness
   * in checkCompleteness.
   */
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  
  /**
   * This is called by the parser when it encounters a scalar item, such as
   * a string or an integer. The parser doesn't differentiate between the
   * types, though, so this method needs to figure it out.
   */
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  /**
   * This member function should check the data in this class for correctness
   * to the extent that is possible. It should not perform a deep check as this
   * function will be called every time a sequence/mapping close event is
   * issued. This signals to the instance that it won't be given any more
   * data by the parser. The only other method that could be called is
   * createObj() (see below).
   */
  virtual bool checkCompleteness();
private:
  void te(char* prefix, const char* fn) throw(BadConfigException);
};

/**
 * This class doesn't implement any functionality. Instead it serves as an
 * "interface" for other items, which subclass it. We need to use a pure virtual
 * class here because we need to have a container of different items and be
 * able to call functions on them without knowing the specific type at compile-
 * time. This lets us figure out the type at run-time. Any functions that a
 * derived class doesn't implement are still there in the ConfigItem and get
 * called if missing in the class.
 */
class VirtualItem : public ConfigItem {
public:
  VirtualItem();
  virtual ~VirtualItem();
  
  virtual bool checkCompleteness() = 0;
};

/**
 * Serves as an initiating element during parsing. Isn't ever on the stack and
 * simply creates the RootMapping.
 */
class BaseItem : public VirtualItem {
public:
  BaseItem();
  virtual ~BaseItem();
  
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual VirtualItem* getMapping() throw(BadConfigException);
  virtual bool checkCompleteness();
};

/**
 * BaseItem generates this when parsing starts. This class expects mappings
 * for cursors, menus or effects. That is, it provides for one of each of
 * EffectsSequence, MenusSequence, CursorsSequence.
 */
class RootMapping : public VirtualItem {
public:
  RootMapping();
  ~RootMapping();
  
  virtual VirtualItem* getSequence() throw(BadConfigException);
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual bool checkCompleteness();
  /**
   * Given a pointer to an allocated configdata structure, populates it with
   * the data from the parsed config.
   */
  void createObj(configdata*);
  
protected:
  map<string, VirtualItem*> data;
  
private:
  static const unsigned short ANY = 0;
  static const unsigned short MENUS = 1;
  static const unsigned short CURSORS = 2;
  static const unsigned short EFFECTS = 3;
  
  unsigned short expectedSection;
};



/********* ACTIONS *********/

/** Base class for action definitions. */
class ActionItem {
public:
  ActionItem();
  virtual ~ActionItem();
  
  virtual Action* createObj() throw(BadConfigException) = 0;
  virtual bool checkCompleteness() = 0;
  virtual void setNextItem(string* data) throw(BadConfigException) = 0;
};
class StatefulKeyActionItem : public ActionItem {
public:
  StatefulKeyActionItem();
  virtual ~StatefulKeyActionItem();
  
  virtual Action* createObj() throw(BadConfigException);
  virtual bool checkCompleteness();
  virtual void setNextItem(string* data) throw(BadConfigException);
  
private:
  map<string, string> data;
  bool expectingKey;
  string lastKey;
};
class KeyActionItem : public ActionItem {
public:
  KeyActionItem();
  virtual ~KeyActionItem();
  
  virtual Action* createObj() throw(BadConfigException);
  virtual bool checkCompleteness();
  virtual void setNextItem(string* data) throw(BadConfigException);
  
private:
  map<string, string> data;
  bool expectingKey;
  string lastKey;
};
class CommandActionItem : public ActionItem {
public:
  CommandActionItem();
  virtual ~CommandActionItem();
  
  virtual Action* createObj() throw(BadConfigException);
  virtual bool checkCompleteness();
  virtual void setNextItem(string* data) throw(BadConfigException);
  
private:
  map<string, string> data;
  bool expectingKey;
  string lastKey;
};
class MenuActionItem : public ActionItem {
public:
  MenuActionItem();
  virtual ~MenuActionItem();
  
  virtual Action* createObj() throw(BadConfigException);
  Action* createObj(configdata* cd) throw(BadConfigException);
  virtual bool checkCompleteness();
  virtual void setNextItem(string* data) throw(BadConfigException);
  
private:
  map<string, string> data;
  bool expectingKey;
  string lastKey;
};
class GesturesActionItem : public ActionItem {
public:
  GesturesActionItem();
  virtual ~GesturesActionItem();
  
  virtual Action* createObj() throw(BadConfigException);
  Action* createObj(configdata* cd) throw(BadConfigException);
  virtual bool checkCompleteness();
  virtual void setNextItem(string* data) throw(BadConfigException);
  
private:
  map<string, string> data;
  bool expectingKey;
  string lastKey;
};
class NoActionItem : public ActionItem {
public:
  NoActionItem();
  virtual ~NoActionItem();
  
  virtual Action* createObj() throw(BadConfigException);
  virtual bool checkCompleteness();
  virtual void setNextItem(string* data) throw(BadConfigException);
};


/********* CURSORS *********/

/** The contents of the items sequence. ie. ["dotbox", "arrow"] */
class CursorItems : public VirtualItem {
public:
  CursorItems();
  virtual ~CursorItems();
  
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual bool checkCompleteness();
  vector<string>* createObj();
protected:
  vector<string> data;
};

/** {name: x, items: [CursorItems]} */
class CursorMapping : public VirtualItem {
public:
  CursorMapping();
  virtual ~CursorMapping();
  
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual VirtualItem* getSequence() throw(BadConfigException);
  virtual bool checkCompleteness();
  void createObj(cursor_map&);
  
protected:
  pair<string, CursorItems*> data; // name: stuff, items: [list]
  
private:
  bool expectingKey;
  string lastKey;
  bool createdSequence;
};

/** [CursorMapping, ...] */
class CursorsSequence : public VirtualItem {
public:
  CursorsSequence();
  virtual ~CursorsSequence();
  
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual VirtualItem* getMapping() throw(BadConfigException);
  virtual bool checkCompleteness();
  void createObj(configdata* cd);
  
protected:
  vector<CursorMapping*> data;
};



/********* MENUS *********/

/** {label : x, action : y, ...} */
class MenuItemMapping : public VirtualItem {
public:
  MenuItemMapping();
  virtual ~MenuItemMapping();
  
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual bool checkCompleteness();
  menu_item* createObj();
  
protected:
  pair<string, ActionItem*> data; // label / action
  
private:
  bool expectingLabel;
  bool expectingAction;
  string lastKey;
};

/** [MenuItemMapping, ...] */
class MenuItems : public VirtualItem {
public:
  MenuItems();
  virtual ~MenuItems();
  
  virtual VirtualItem* getMapping() throw(BadConfigException);
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual bool checkCompleteness();
  vector<menu_item*>* createObj();
  
protected:
  vector<MenuItemMapping*> data;
};

/** {name : x, items : MenuItems} */
class MenuMapping : public VirtualItem {
public:
  MenuMapping();
  virtual ~MenuMapping();
  
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual VirtualItem* getSequence() throw(BadConfigException);
  virtual bool checkCompleteness();
  void createObj(menu_map& mm);
  
protected:
  pair<string, MenuItems*> data; // name / items
  
private:
  bool expectingKey;
  string lastKey;
  bool createdSequence;
};

/** [MenuMapping, ...] */
class MenusSequence : public VirtualItem {
public:
  MenusSequence();
  virtual ~MenusSequence();
  
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual VirtualItem* getMapping() throw(BadConfigException);
  virtual bool checkCompleteness();
  void createObj(configdata* cd);
  
protected:
  vector<MenuMapping*> data;
};



/********* EFFECTS *********/

/*** TriggerMapping ***/

/** {button : x, edge : y, class : "asdf"} */
class TriggerMapping : public VirtualItem {
public:
  TriggerMapping();
  virtual ~TriggerMapping();
  
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual bool checkCompleteness();
  Trigger* createObj() throw(BadConfigException);
  
protected:
  map<string, string> data;
  
private:
  bool expectingKey;
  string lastKey;
};


/*** Trigger+Action Mapping ***/
/** {trigger : TriggerMapping, action : x, edge : y, ...} */
class TriggerActionMapping : public VirtualItem {
public:
  TriggerActionMapping();
  virtual ~TriggerActionMapping();
  
  virtual VirtualItem* getMapping() throw(BadConfigException);
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual bool checkCompleteness();
  void createObj(effect&, configdata*);
  
protected:
  pair<TriggerMapping*, ActionItem*> data;
  
private:
  bool alreadyCreatedMapping;
  bool expectingAction;
};


/*** EffectsSequence ***/
/** [TriggerActionMapping, ...] */
class EffectsSequence : public VirtualItem {
public:
  EffectsSequence();
  virtual ~EffectsSequence();
  
  virtual void setNextItem(VirtualItem* next) throw(BadConfigException);
  virtual void setNextItem(yaml_char_t* data, size_t length)
                                            throw(BadConfigException);
  virtual VirtualItem* getMapping() throw(BadConfigException);
  virtual bool checkCompleteness();
  void createObj(configdata* cd);
  
protected:
  vector<TriggerActionMapping*> data;
};



/*** HELPERS ***/
/** Thrown when a conversion fails. */
class BadConversion : public runtime_error {
public:
  BadConversion(const std::string& s) : runtime_error(s) {}
};

/**
 * Based on the given name of the action, creates an empty action object.
 * @param value name of the action (ie. "stateful key")
 * @return new empty ActionItem.
 */
ActionItem* createNewActionItem(string& value);

/** Convert a string into an unsigned int. */
unsigned int str2int(const string& str) throw(BadConversion);
/** Convert a string into a char*. */
char* str2char(const string& str);
/** Produce a KeySym from a string or throw an exception. */
KeySym str2keysym(const char* str, string kindOfAction)
                                                throw(BadConfigException);
/** Produce an int button value from a string or throw an exception. */
unsigned int str2button(const char* str) throw(BadConfigException);
/** Return the place constant from a string. */
const Place* str2place(string& stredge);
/** Convert a vector of menu_items into an array of them. Performs a copy. */
menu_item* flattenMenu(vector<menu_item*>& items);
/** Convert a vector of cursors into an arrat of them. Performs a copy. */
unsigned int* flattenCursors(vector<unsigned int>& items);
/** Trims the string of whitespace from both sides. */
string trim(const string& o);

#endif	/* _CONFIGDATA_H */

